#!/usr/bin/env python
"""
Exploding sheeps -minesweeper game.

Licensed under GPL v2.0, see <URL: http://www.gnu.org>
for full text.

(c) 2005-2006 Pasi Savolainen <pasi.savolainen@iki.fi>
"""

import pygtk
pygtk.require('2.0')
import gtk, gtk.glade, pango, gobject
import exsheep
import math, time, random, pickle
import ConfigParser
# From GNOME Mines:
# Small  8x8 10     64/10 = 6.4
# Medium 16x16 40   256/40 = 6.4
# Large  30x16 99   480/99 = 4.848

BOOM, GAMEOVER, LIFE, SCOREINC, NEWGAME, EXPLOSION = xrange(6)

BANNER_TIMER = 50 # ms
BANNER_SEC = 1000/BANNER_TIMER
BASIC_TIMER = 500
BASIC_SEC = 1000/BASIC_TIMER
FUNNIES_COUNT = 4

# consts
#un_board = gtk.gdk.Color(0, 0x9500, 0xde00)
#cl_board = gtk.gdk.Color(0, 0xa500, 0xee00)
#un_board = gtk.gdk.Color(0xbd00, 0xdc00, 0xf000)
#cl_board = gtk.gdk.Color(0xd100, 0xe700, 0xf000)
cl_board = gtk.gdk.Color(0xaf00, 0xda00, 0xf800)
#un_board = gtk.gdk.Color(0x9b00, 0xd200, 0xf000)
un_board = gtk.gdk.Color(0x2d00, 0x9d00, 0xe900)
gray_c = gtk.gdk.Color(0x6000, 0x6000, 0x6000);
gray_cd = gtk.gdk.Color(0x8000, 0x8000, 0x8000);

clr_idx = ( gtk.gdk.Color(0, 0, 0), # 0 black
            gtk.gdk.Color(0, 0, 0xFFFF), # 1 blue
            gtk.gdk.Color(0, 0xA000, 0), # 2 green
            gtk.gdk.Color(0xFFFF, 0, 0), # 3 red
            gtk.gdk.Color(0, 0, 0x7FFF), # 4 dark blue
            gtk.gdk.Color(0xA000, 0, 0), # 5 dark red
            gtk.gdk.Color(0, 0xFFFF, 0xFFFF), # 6 cyan
            gtk.gdk.Color(0xa000, 0, 0xa000), # 7 violet
            gtk.gdk.Color(0xa000, 0xa000, 0)) # 8 mold
scoreinc = gtk.gdk.Color(0xffff, 0xffff, 0)
FC_STATS, FC_LINE_DONE, FC_GAMEOVER = "stats", "linedone", "gameover"
FC_VISITED, FC_BLOWN, FC_LIFE_GAINED = "visited", "blown", "life"
FC_SCORE_INCREASE = "score_increase"
class Banner:
    PERMANENT = -1
    CENTER, FILL, CORNER, POSITIONED = range(4)
    def __init__(self, btype, duration, location, x=0, y=0, dx=0, dy=0, data=None):
        self.btype = btype
        self.duration = duration
        self.location = location
        self.x, self.y = x, y
        self.dx, self.dy = dx, dy
        self.data = data
        self.ticks = 0        
    def tick(self, nticks=1):
        self.x += self.dx
        self.y += self.dy
        self.ticks += nticks
    def permanent(self):
        return self.duration == Banner.PERMANENT
    def done(self):
        return self.ticks > self.duration and not self.permanent()
    def __str__(self):
        return "<banner bt: %s, loc: %d, ticks: %d, xy: (%d,%d)>" % \
               (self.btype, self.location, self.ticks, self.x, self.y)

class GamePrefs:
    SMALL, MEDIUM, LARGE, CUSTOM = xrange(4)
                # id    width, height, mines, text-id, ordernum
    SIZE_SETS = { SMALL: (8, 8, 10, "small", 0),
                  MEDIUM: (16, 16, 40, "medium", 1),
                  LARGE: (32, 16, 99, "large", 2) }
    def __init__(self, mode=4, w = 30, h=16, mines=99):
        self.mode = mode
        self.w = w
        self.h = h
        self.mines = mines
        self.load()
        
    def load(self, fname='exsheeprc'):
        DEF_CONFIG = { 'mode': 0,
                       'width': 30, 'height': 16, 'mines': 99 }
        config = ConfigParser.ConfigParser(DEF_CONFIG)
        config.read(fname)
        ret = DEF_CONFIG
        ret.update(config.defaults())
        self.mode = self.CUSTOM
        for i in self.SIZE_SETS.keys():
            print "ee",i
            if int(ret['mode']) == self.SIZE_SETS[i][4]:
                print "setting mode: ", self.SIZE_SETS[i][3]
                self.mode = self.SIZE_SETS[i][4]
        self.width = int(ret['width'])
        self.height = int(ret['height'])
        self.mines = int(ret['mines'])
        
    def save(self, fname='exsheeprc'):
        CONF = { 'mode': self.mode,
                 'width': self.w, 'height':self.h,
                 'mines': self.mines }
        config = ConfigParser.ConfigParser(CONF)
        config.write(open(fname, 'w'))
        
    def valid_mode(self, w, h, mines):
        # can't have too many mines
        if w*h <= mines:
            return False;
        return True
    
    def set_new_mode(self, mode, w=8, h=8, mines=10):
        """Set new mode """
        if mode == self.CUSTOM and not self.valid_mode(w, h, mines):
            print "Ivalid mode"
            return False
        else:
            self.w = w
            self.h = h
            self.mines = mines
        self.mode = mode
        self.save()
        return True
    
    def reverse_size(self, w, h, mines):
        for s in xrange(self.CUSTOM):
            ss = self.SIZE_SETS[s]
            if ss[0] == w and ss[1] == h and ss[2] == mines:
                return s
        return self.CUSTOM

    def whm(self):
        if self.mode == self.CUSTOM:
            return (self.w, self.h, self.mines)
        return self.SIZE_SETS[self.mode][:3]

class GameScore:
    def __init__(self, points, when, lines, mines, sstart, slength):
        self.setup(points, when, lines, mines, sstart, slength)
        
    def setup(self, points, when, lines, mines, sstart, slength):
        self.points = points
        self.when = when
        self.lines = lines
        self.mines = mines
        self.sstart = sstart
        self.slength = slength
        print "gscore: ", points, when, lines, mines, sstart, slength
    def __getstate__(self):
        return (self.points, self.when, self.lines, self.mines,
                self.sstart, self.slength)
        #return self.__dict__
    def __setstate__(self, d):
        self.setup(*d)
        #self.__dict__.update(dict);
        
class GameScores:
    SCORE_FNAME = 'scores.p'
    def __init__(self):
        try:
            self.scores = pickle.load(open(self.SCORE_FNAME))
        except IOError:
            # create list of 'small', 'medium', 'large', 'custom'
            self.scores = [[], [], [], []]
    def add_score(self, size, score):
        self.scores[size].append(score)
        pickle.dump(self.scores, open(self.SCORE_FNAME,'w'))
    def get_scores(self, size):
        return self.scores[size]
        
class GameState:
    def __init__(self, w, h, mines, changed_cb):
        self.bw, self.bh = w, h
        self.board = exsheep.Board(w, h)
        self.mines_on_field = mines
        self.board.populate_area(0, self.bh-1, self.mines_on_field)
        self.score = 0
        self.game_active = True
        self.correct_mines = 0
        self.lines = 0
        self.mines = 0
        self.lifes_used = 0
        self.flags_active = 0
        self.changed_cb = changed_cb
        # timekeeping
        self.game_started = time.time()
        self.game_session_started = time.time()
        self.game_runtime = 0
        self.session_runtime = 0
        self.pause_interaction = False
        self.seq_lines = 0
        
    def get_score(self):
        return GameScore(self.score, time.time(), self.lines, self.mines,
                         self.game_started, self.game_runtime)
        
    def legal_coordinates(self, x, y):
        """ Check validity of coordinates """
        return (x >= 0 and y >= 0 and x < self.bw and y < self.bh)
    
        
    def tick(self):
        """Time tick.
        Keeps up time of game session.
        """
        tt = time.time()
        sessiontime = tt - self.game_session_started
        self.session_runtime = sessiontime
        self.try_score()
        
    def lifes_gained(self, lines=-1):
        # lifes start from 3 and gain 1 at 125, 250, 500, 1000..
        if lines < 0:
            lines = self.lines
        return 3+int(math.log(lines/62.5+1)/math.log(2))
        
    def get_game_runtime(self):
        """Get time spent over whole game (total active runtime)
        """
        return int(self.game_runtime + self.session_runtime)
    
    def try_score(self):
        # is last line done?
        if self.board.line_done() == False or self.seq_lines >= self.bh - 1:
            if self.pause_interaction:
                # replentish mines.
                self.board.populate_area(0, self.seq_lines,
                                         self.mines_on_field - self.board.mines_count())
                self.changed_cb(FC_LINE_DONE)
            self.pause_interaction = False
            self.seq_lines = 0
            return
        if self.pause_interaction:
            self.seq_lines += 1
        else:
            self.pause_interaction = True
            self.seq_lines = 0
        cflags, flags, mines, bl = self.board.remove_bottom_line()
        self.lines += 1
        self.add_score("lines", 1);
        self.correct_mines += cflags
        self.add_score("mines", cflags);
        self.mines += mines
        if mines != cflags:
            print "m!c", mines, cflags, bl
        if self.lifes_gained() - self.lifes_gained(self.lines-1):
            self.add_score("life",
                           self.lifes_gained() - self.lifes_gained(self.lines-1))
            self.changed_cb(FC_LIFE_GAINED)
        self.changed_cb(FC_STATS);
        self.changed_cb(FC_LINE_DONE)


    def mine_density(self):
        return self.bw * self.bh / self.mines_on_field;

        
    def add_score(self, ctype, count, *args):
        add = 0
        if ctype == "lines":
            add =  int(self.mine_density()*10*count)
        elif ctype == "mines":
            add = self.mine_density()*count;
        elif ctype == "life":
            add = int(self.mine_density()*100*count)
        elif ctype == "revealed":
            add = count;
            
        self.score += add
        print "Score: ", ctype, count, "+", add
        
        self.changed_cb(FC_SCORE_INCREASE, add, args)

    def game_over(self):
        return self.lifes_gained() - self.lifes_used <= 0
        
    def click_at(self, x, y, shift, button1, button3):
        if self.pause_interaction or \
               (self.lifes_gained() - self.lifes_used == 0):
            # no action if no interaction allowed
            return
        assert self.legal_coordinates(x, y)
        
        blown, visited = [], []

        if shift:
            if button1:
                self.board.add_mine(x, y)
            else:
                self.remove_mine(x, y)
            visited = self.board.surroundings(x,y);
            
        else:
            if button1:
                blown, visited = self.board.reveal(x, y)
                self.add_score("revealed", len(visited), visited)
                
            elif button3:
                self.board.toggle_flag(x, y)
                self.flags_active = len(self.board.flags)
                visited = [(x, y)]
                
        if blown:
            print len(blown), "mines hit at", blown
            self.lifes_used += 1
            self.changed_cb(FC_BLOWN, blown)
            for cell in blown:
                for scell in self.board.surroundings(*cell):
                    visited.append(scell)
            if self.lifes_gained() - self.lifes_used <= 0:
                print "Game over, sortof"
                self.changed_cb(FC_GAMEOVER)
                
        if visited:
            self.changed_cb(FC_VISITED, visited)

        self.changed_cb(FC_STATS)
        

class EsMain:
    def __init__(self):
        # self.setup_new_game(60, 32, 480)
        self.ww, self.hh = 1, 1
        self.shiftx, self.shifty = 64, 64
        self.notice_at = (0,0)        
        self.setup_images()
        # allow this python script access to the .glade file
        self.wtree = gtk.glade.XML('exsheep.glade')        
        # Connect the glade signals to the python callbacks
        self.wtree.signal_autoconnect(self)
        self.drarea = self.wtree.get_widget('esdraw')
        self.conf = GamePrefs()
        self.setup_new_game(*(self.conf.whm()))
        
        # start basic ticker
        gobject.timeout_add(BASIC_TIMER, self.basic_timer)

        
    def setup_images(self):
        """Setup images for application.
        Images will be later resized and prerendered on drawing surfaces.
        """
        def load_image(name):
            return gtk.gdk.pixbuf_new_from_file('graphics/%s' % name)
        
        self.flagbuf = load_image('flag.png')
        # several wave images.
        wavebufs = []
        for i in range(3):
            wavebufs.append(load_image('wave_%d.png' %i))
        self.wavebufs = tuple(wavebufs)
        self.waves = ()
        # load images to buffers
        self.boombuf = load_image('boom.png')
        self.lboombuf = load_image('little_boom.png')
        self.lifebuf = load_image('plus_1_life.png')
        self.gobuf = load_image('game_over.png')
        self.newgamebuf = load_image('new_game.png')

    def get_area_center(self, area):
        minx, miny = self.game_session.bw, self.game_session.bh
        maxx, maxy = 0, 0
        for spot in area:
            minx, miny = min(minx, spot[0]), min(miny, spot[1])
            maxx, maxy = max(maxx, spot[0]), max(maxy, spot[1])
        return minx+(maxx-minx)/2, miny+(maxy-miny)/2
        
    def session_changed(self, ctype, *args):
        if ctype == FC_STATS:
            self.update_stats();
            return
        if ctype == FC_VISITED:
            self.redraw_partial(args[0], [])
            return
        if ctype == FC_BLOWN:
            # self.redraw_partial([], args[0])
            self.add_blown_banners(args[0])
            return
        if ctype == FC_LINE_DONE:
            self.drarea.queue_draw()
            return
        if ctype == FC_SCORE_INCREASE:
            if args[0] < 2:
                return
            if args[1]:
                # print "Moo", args
                atx, aty = self.get_area_center(args[1][0])
                # print "Score changed for area", args, atx, aty
                
                self.add_banner (Banner (SCOREINC, 1*BANNER_SEC, Banner.POSITIONED,
                                         atx, aty, dy = -0.1, data = args[0]))
            else:
                print "Scorechange", args
                self.add_banner (Banner (SCOREINC, 1*BANNER_SEC, Banner.CORNER,
                                         dy = -0.1, data = args[0]))
            return
        if ctype == FC_GAMEOVER:
            # self.start_banner_tick(GAMEOVER, -1)
            self.add_banner (Banner (GAMEOVER, Banner.PERMANENT, Banner.FILL))
        
        print "*** Oops, session changed, should I do something with:", ctype, args

    def get_current_size(self):
        return self.conf.reverse_size(self.game_session.bw,
                                      self.game_session.bh,
                                      self.game_session.mines_on_field)
        
    def save_score(self):
        if not self.__dict__.has_key('game_session') or \
               self.game_session.score == 0:
            print "can't save score, no gs or no score."
            return
        game_scores = GameScores()
        game_scores.add_score(self.get_current_size(),
                              self.game_session.get_score())
        
    def setup_new_game(self, w, h, mines):
        self.save_score()
        self.game_session = GameState(w, h, mines, self.session_changed)
        # banners
        self.banners = [] # banner timers.
        self.banner_run = False # banner running?
        # self.start_banner_tick(NEWGAME, 1*BANNER_SEC)
        self.add_banner (Banner(NEWGAME, 3*BANNER_SEC, Banner.CENTER))

    def add_banner(self, banner):
        self.banners.append(banner)
        if self.ww != 1:
            self.drarea.queue_draw()
        if self.banners and not self.banner_run:
            self.banner_run = True
            gobject.timeout_add(BANNER_TIMER, self.banner_timer)
        
    def basic_timer(self):
        self.game_session.tick()
        now = self.game_session.get_game_runtime() # int(time.time()) - self.game_started
        seconds, minutes, hours = now % 60, (now/60) % 60, (now/60)/60
        self.update_stat('stat_time',
                         "Time: %d:%02d:%02d" % (hours, minutes, seconds))
        # self.draw_funnies()
        return True

    def banner_timer(self):
        redraw = False
        areas = []
        for banner in self.banners:
            # redraw currently occupied area if banner not permanent.
            if not banner.permanent() and self.ww != 1:
                areas.append(self.estimate_area(banner))
            banner.tick();            
            # areas.append(self.estimate_area(banner))
            if banner.done():
                print "Removing banner", banner
                self.banners.pop(self.banners.index(banner))
            elif not banner.permanent():
                redraw = True

        if redraw or areas:
            if areas:
                for area in areas:
                    self.drarea.queue_draw_area(area.x, area.y,
                                                area.width, area.height)
        else:
            self.banner_run = False
            self.drarea.queue_draw()
            return False
        return True
    
    def banner_timer_deprec(self):
        redraw = 0
        for i in self.banners.keys():
            if self.banners[i] > 0:
                self.banners[i] -= 1
                redraw = 1
        if redraw:
            pass
        else:
            self.banner_run = False
            self.drarea.queue_draw()
            return False
        return True

    def start_banner_tick_deprec(self, obj, time):
        self.banners[obj] = int(time)
        if self.banner_run == False:
            self.banner_run = True
            gobject.timeout_add(BANNER_TIMER, self.banner_timer)

    def add_blown_banners(self, blown):
        # self.banners_blown.append(blown)
        # self.start_banner_tick(BOOM, 1*BANNER_SEC)
        self.add_banner (Banner (BOOM, 1*BANNER_SEC, Banner.FILL))
        
        for site in blown:
            self.add_banner (Banner (EXPLOSION, 3*BANNER_SEC, Banner.POSITIONED,
                                     site[0], site[1]))
            
    

    def calc_board_coords(self, x, y):
        tilew, tileh = self.calc_tile_size()
        return (int((x-self.shiftx)/tilew), int((y-self.shifty)/tileh))

    
    def on_eventbox1_button_press_event(self, *args):
        eb, ge = args
        bx, by = self.calc_board_coords(*ge.get_coords())
        if not self.game_session.legal_coordinates(bx, by):
            return
        self.notice_at = (bx, by)
        self.button_at(bx, by, True)
        
    def on_eventbox1_button_release_event(self, *args):
        if self.game_session.game_over():
            """
            self.setup_new_game(self.game_session.bw,
                                self.game_session.bh,
                                self.game_session.mines_on_field)
                                """
            self.setup_new_game(*(self.conf.whm()))
            return
        eb, ge = args
        bx, by = self.calc_board_coords(*ge.get_coords())
        if not self.game_session.legal_coordinates(bx, by):
            return
        self.game_session.click_at(bx, by,
                                   ge.state & gtk.gdk.SHIFT_MASK,
                                   ge.state & gtk.gdk.BUTTON1_MASK,
                                   ge.state & gtk.gdk.BUTTON3_MASK)
        
        """
        if len(blown):
            print len(blown), "mines hit at", blown
            self.lifes_used += 1
            if self.lifes_gained() - self.lifes_used <= 0:
                self.start_banner_tick(GAMEOVER, -1)
            else:
                pass #self.start_banner_tick(BOOM, 1*BANNER_SEC)
        """

        """
        row, rep_mines = 0, 0
        lines = self.clines
        while board.line_done() and row < self.bh:
            cflags, flags, mines, bl = board.remove_bottom_line()            
            rep_mines += mines
            self.cmines += cflags
            self.mines += mines
            self.clines += 1
            self.badflags += flags - cflags
            print "row finished, correct", cflags, "total", flags, "mines", mines
            row += 1        
        # removed some lines, repopulate board.
        if rep_mines > 0:
            board.populate_area(0, row-1, rep_mines)
        """
        # gained one life
        # if self.lifes_gained()-self.lifes_gained(lines):
        #    self.start_banner_tick(LIFE, 3*BANNER_SEC)
        # self.update_stats()
        #if row > 0:
        #    eb.queue_draw()
        #else:
        #    self.redraw_partial(blown+visited+[(bx, by)], blown)
        
    def update_stat(self, sname, s):
        w = self.wtree.get_widget(sname)
        w.set_markup(s)
        
    def update_stats(self):
        gs = self.game_session
        #stat_life        
        self.update_stat('stat_life', "Lifes: %d" %(
            gs.lifes_gained()-gs.lifes_used))
        #stat_lines
        self.update_stat('stat_lines', "Lines: %d" % gs.lines)
        #stat_mines
        self.update_stat('stat_mines', "Mines: %d/%d, %d flags" % (
            gs.correct_mines, gs.mines, gs.flags_active))
        #stat_score
        self.update_stat('stat_score', "Score: %d" % gs.score)
        #stat_time
        # self.update_stat('stat_time', "Time: HH:MM:SS")

    def button_at(self, bx, by, press):
        self.draw_tile_notice(bx, by, press)

    def calc_tile_size(self):
        tile = int( min (float(self.ww)/self.game_session.bw,     # tile width
                     float(self.hh)/(self.game_session.bh+1))) # tile height
        return (tile, tile)
        
        

    def draw_tile_notice(self, bx, by, press):
        gw = self.drarea.window
        #tsx = float(self.ww)/self.bw
        #tsy = float(self.hh)/(self.bh+1)
        tsx, tsy = self.calc_tile_size()
        gc = gw.new_gc()
        content = self.game_session.board.us_char(bx, by)
        black = gtk.gdk.Color(0,0,0,0)
        recession = 2
        if press:
            gw.draw_rectangle(gc, False,
                              int(bx*tsx)+recession+self.shiftx, int(by*tsy)+recession+self.shifty,
                              int(tsx)-recession*2, int(tsy)-recession*2)
            
    def redraw_partial(self, tiles, blown):
        gw = self.drarea.window
        tsx, tsy = self.calc_tile_size()
        gc = gw.new_gc()
        tiles.append(self.notice_at);
        for i in tiles:
            #if i[1] < self.bh: # NFI why this was here.
            self.draw_tile(gw, gc, i[0], i[1], tsx, tsy, self.layout)
        self.draw_banners(gw, gc, self.layout)
        for i in blown:
            self.draw_tile_blown(gw, gc, i[0], i[1], tsx, tsy)
            
    def draw_funnies(self):
        """Draw some animations across board to make it livelier"""
        gw = self.drarea.window
        tsx, tsy = self.calc_tile_size()
        gc = gw.new_gc()
        tiles = self.board.get_covered()
        for i in xrange(FUNNIES_COUNT):
            x, y = random.choice(tiles)
            gw.draw_drawable(gc, random.choice(self.waves),
                             0, 0, int(x*tsx), int(y*tsy), -1, -1)
        
            
    def rescale_tile_img(self, buf, tilew, tileh):
        imw, imh = buf.get_width()+4, buf.get_height()+4
        sf = min (float(tilew)/imw, float(tileh)/imh)
        return buf.scale_simple(int(imw*sf), int(imh*sf), gtk.gdk.INTERP_HYPER)

    def rescale_images(self, gw, gc, un_board, board):
        tilew, tileh = self.calc_tile_size()
        self.flagdraw = self.rescale_tile_img(self.flagbuf, tilew, tileh)
        self.lboomdraw = self.rescale_tile_img(self.lboombuf, tilew, tileh)
        wvs, wvsc = [], []        
        gc.set_rgb_fg_color(un_board);
        for i in self.wavebufs:
            # unknown area
            img = gtk.gdk.Pixmap(gw, int(tilew), int(tileh))
            img.draw_rectangle(gc, True, 0, 0, int(tilew)+1, int(tileh)+1);
            wv = self.rescale_tile_img(i, tilew, tileh)
            img.draw_pixbuf(gc, wv, 0, 0, 0, 0)
            wvs.append(img)
        gc.set_rgb_fg_color(board);
        for i in self.wavebufs:
            # revealed area
            img = gtk.gdk.Pixmap(gw, int(tilew), int(tileh))
            img.draw_rectangle(gc, True,  0, 0, int(tilew)+1, int(tileh)+1);
            wv = self.rescale_tile_img(i, tilew, tileh)
            img.draw_pixbuf(gc, wv, 0, 0, 0, 0)
            wvsc.append(img)
        self.waves = tuple(wvs)
        self.waves_cleared = tuple(wvsc)
        
    def on_esdraw_expose_event(self, w, event):
        # print event.area
        # self.full_redraw(w)
        self.grab_dimensions(w)
        self.area_redraw(w, event.area)

        
    def area_redraw(self, w, area):
        start_x, start_y = self.calc_board_coords(area.x, area.y)
        # start_x -= 1; start_y -= 1
        end_x, end_y = self.calc_board_coords(area.x+area.width, area.y+area.height)
        # end_x += 1; end_y += 1
        if not self.game_session.legal_coordinates(start_x, start_y) or \
           not self.game_session.legal_coordinates(end_x, end_y):
            print "Can't do update:", start_x, start_y, "-", end_x, end_y,
            print " / ", area.x, area.y, "-", area.width, area.height
            self.full_redraw(w)
            return
        #print "Do update:", start_x, start_y, "-", end_x, end_y,
        #print " / ", area.x, area.y, "-", area.width, area.height
        gw = w.window
        gc = gw.new_gc()
        tilew, tileh = self.calc_tile_size()
        for y in range (start_y, end_y+1):
            for x in range (start_x, end_x+1):
                self.draw_tile (gw, gc, x, y, tilew, tileh, self.layout)

        # self.draw_lastline (gw, gx, tilew, tileh, layout);
        self.draw_banners (gw, gc, self.layout)
        
    def grab_dimensions(self, w):
        gw = w.window
        if (self.ww, self.hh) == gw.get_size():
            return
        gc = gw.new_gc()
        # size of board changed
        self.ww, self.hh = gw.get_size()
        self.rescale_images(gw, gc, un_board, cl_board)
        pc = w.get_pango_context()
        tsx, tsy = self.calc_tile_size()
        fdesc = pango.FontDescription("sans bold %d" % int(tsy/1.8))            
        self.layout = pango.Layout(pc)
        self.layout.set_font_description(fdesc)
        self.shifty = 0
        self.shiftx = (self.ww - tsx * self.game_session.bw) / 2
        # self.layout.set_alignment(pango.ALIGN_CENTER)

        
    def full_redraw(self, w):
        gw = w.window
        gc = gw.new_gc()        
        tsx, tsy = self.calc_tile_size()
        layout = self.layout
        # print "ps", pango.PIXELS(200*pango.SCALE), int(tsy - 8), fdesc.get_size(), pango.PIXELS(fdesc.get_size())
        
        for y in range(0, self.game_session.bh):            
            for x in range(0, self.game_session.bw):
                self.draw_tile(gw, gc, x, y, tsx, tsy, layout)
                #layout.set_text("%dx%d"%(x,y));
        #self.draw_grid(gw, gc, tsx, tsy, gray_cd)
        self.draw_lastline(gw, gc, tsx, tsy, layout)
        self.draw_banners(gw, gc, layout)

    def draw_tile_blown(self, gw, gc, x, y, tsx, tsy):
        gw.draw_pixbuf(gc, self.lboomdraw, 0, 0, int(x*tsx)+2, int(y*tsy)+2)
        
    def draw_tile(self, gw, gc, x, y, tsx, tsy, layout):
        content = self.game_session.board.us_char(x,y)
        if content == exsheep.UNKNOWN:
            # unrevealed and not flag
            
            #gc.set_rgb_fg_color(un_board);
            #gw.draw_rectangle(gc, True,  int(x*tsx), int(y*tsy),
            #                  int(tsx)+1, int(tsy)+1);
            
            # pseudo-random position dependant wave permutator
            wv = self.waves[((x+1)*(y+1)+(x+2)*(y+1)+(x+1)*(y+2)+x+y)%len(self.waves)]
            gw.draw_drawable(gc, wv, 0, 0,
                             int(x*tsx)+self.shiftx, int(y*tsy)+self.shifty,
                             -1, -1)
            return
        color = 0
        if content == exsheep.CFLAG:
            gc.set_rgb_fg_color(un_board)
            gw.draw_rectangle(gc, True,
                              int(x*tsx)+self.shiftx, int(y*tsy)+self.shifty,
                              int(tsx)+1, int(tsy)+1);
            gw.draw_pixbuf(gc, self.flagdraw, 0, 0,
                           int(x*tsx)+2+self.shiftx, int(y*tsy)+2+self.shifty)
            return
        else:
            color = clr_idx[content]
            gc.set_rgb_fg_color(color)
            wv = self.waves_cleared[((x+1)*(y+1)+(x+2)*(y+1)+(x+1)*(y+2)+x+y)%len(self.waves_cleared)]
            gw.draw_drawable(gc, wv, 0, 0,
                             int(x*tsx)+self.shiftx, int(y*tsy)+self.shifty,
                             -1, -1)
        if content != 0:
            #layout.set_text(self.board.pr_char(x, y))
            layout.set_text("%s" % content)
            extw, exth = layout.get_pixel_size();
            extw = (tsx - extw)/2
            exth = (tsy - exth)/2
            gw.draw_layout(gc, x*tsx+extw+self.shiftx,
                           y*tsy+exth+self.shifty, layout)
        
    def draw_grid(self, gw, gc, tsx, tsy, gridcolor):
        gc.set_rgb_fg_color(gridcolor)
        for i in range(max(self.bh, self.bw)):
            if i < self.bw:
                gw.draw_line(gc, i*tsx, 0, i*tsx, self.hh)
            if i < self.bh:
                gw.draw_line(gc, 0, (i+1)*tsy, self.ww, (i+1)*tsy)

    def draw_lastline(self, gw, gc, tsx, tsy, layout):
        # get mine counts at 'one over the board in y-direction'
        lastline = self.game_session.board.get_minecounts(self.game_session.bh)
        grayv = 0x6000
        gc.set_rgb_fg_color(gtk.gdk.Color(grayv, grayv, grayv))
        for x in range(self.game_session.bw):
            if lastline[x] == 0:
                continue
            layout.set_text("%d" % lastline[x])
            extw, exth = layout.get_pixel_size();
            extw = (tsx - extw)/2
            exth = (tsy - exth)/2
            gw.draw_layout(gc, x*tsx+extw+self.shiftx,
                           self.game_session.bh*tsy+exth+self.shifty, layout)

            
    def draw_text(self, gw, gc, text, x, y, layout,
                  center=False, cdx=0, cdy=0, color=None, line=0):
        # print "dtext", text, x, y, center, line
        if not color:
            color = gtk.gdk.Color(0x7000, 0x7000, 0x7000)
        gc.set_rgb_fg_color(color)
        layout.set_text(text)
        extw, exth = layout.get_pixel_size()
        if center:
            x = x + cdx/2 - extw/2
            y = y + cdy/2 - exth/2
        if line:
            y += exth * line
        gw.draw_layout(gc, int(x+self.shiftx), int(y+self.shifty), layout)
        
    def estimate_area(self, banner):
        area = gtk.gdk.Rectangle()
        self.layout.set_text("+%d points." % 10000)
        extw, exth = self.layout.get_pixel_size()
        tilew, tileh = self.calc_tile_size()
        if banner.btype == BOOM:
            pass
        elif banner.btype == NEWGAME:
            pass
        elif banner.btype == EXPLOSION:
            area.x = self.shiftx + banner.x * tilew
            area.y = self.shifty + banner.y * tileh
            area.width = tilew
            area.height = tileh
        elif banner.btype == SCOREINC:
            if banner.location == Banner.POSITIONED:
                area.x = self.shiftx + banner.x * tilew
                area.y = self.shifty + banner.y * tileh
                area.width = extw
                area.height = exth
            else:
                area.x = self.shiftx
                area.y = self.shifty
                area.width = extw
                area.height = exth * 5
        else:
            print "ught, can't handle:", banner
        return area
    def draw_banners(self, gw, gc, layout):
        # print "draw_banners"
        line = 0
        tilew, tileh = self.calc_tile_size()
        for banner in self.banners:
            if banner.btype == BOOM:
                self.draw_centered(gw, gc, 0.8, self.boombuf)
            elif banner.btype == LIFE:
                self.draw_positioned(gw, gc, self.lifebuf, 0.2, 0.1, 0.1)
            elif banner.btype == NEWGAME:
                self.draw_centered(gw, gc, 0.8, self.newgamebuf)
            elif banner.btype == GAMEOVER:
                self.draw_positioned(gw, gc, self.gobuf, 1, 0, 1)
            elif banner.btype == EXPLOSION:
                self.draw_tile_blown(gw, gc, banner.x, banner.y, tilew, tileh)
            elif banner.btype == SCOREINC:
                if banner.location == Banner.POSITIONED:
                    self.draw_text(gw, gc, "+%d" % banner.data,
                                   banner.x*tilew+self.shiftx,
                                   banner.y*tileh+self.shifty,
                                   layout, color=scoreinc)
                else:
                    self.draw_text(gw, gc, "+%d points" % banner.data,
                                   2, 2, layout, line=line)
                line += 1
            else:
                print "unhandled banner:", banner
        
    def draw_centered(self, gw, gc, proc, buf):
        self.draw_positioned(gw, gc, buf, proc, 0.5, 0.5)
        
    def draw_positioned(self, gw, gc, buf, proc, posw, posh):
        ww, wh = gw.get_size()
        imw, imh = buf.get_width(), buf.get_height()
        iww, ihw = int(ww*proc), int(wh*proc)
        sf = min(float(iww)/imw, float(ihw)/imh)
        bufdraw = buf.scale_simple(int(imw*sf), int(imh*sf), gtk.gdk.INTERP_HYPER)
        offx, offy = posw*(ww-imw*sf), posh*(wh-imh*sf)
        gw.draw_pixbuf(gc, bufdraw, 0, 0, int(offx), int(offy))
        
    def on_new1_activate(self, *args):
        self.setup_new_game(*(self.conf.whm()))

    def on_highscores1_activate(self, *args):
        def timerender():
            pass
        tv = self.wtree.get_widget('hs_tv')
        list_store = gtk.ListStore(int, str, int, int, str, str)
        tv.set_model(list_store)
        crt = gtk.CellRendererText        
        tv.append_column(gtk.TreeViewColumn('Score', crt(),text=0))
        tv.append_column(gtk.TreeViewColumn('When', crt(), text=1))
        tv.append_column(gtk.TreeViewColumn('Lines', crt(),text=2))
        tv.append_column(gtk.TreeViewColumn('Mines', crt(),text=3))
        tv.append_column(gtk.TreeViewColumn('Started', crt(), text=4))
        tv.append_column(gtk.TreeViewColumn('Time', crt(), text=5))
        size = self.get_current_size()
        self.wtree.get_widget('hs_size_selector').set_active(size)
        self.populate_hs(tv, size)
        self.wtree.get_widget('highscores').show()
        
    def populate_hs(self, tv, size):
        scores = GameScores().get_scores(size)
        m = tv.get_model()
        for score in scores:
            m.append((score.points, score.when, score.lines, score.mines,
                      score.sstart, score.slength))
        
    def on_hs_closebutton_clicked(self, *args):
        self.wtree.get_widget('highscores').hide()
        
    def on_preferences1_activate(self, *args):
        self.wtree.get_widget('preferencesdialog1').show()
        
    def on_preferences_closebutton_clicked(self, *args):
        for field_size in GamePrefs.SIZE_SETS.keys():
            ss = GamePrefs.SIZE_SETS[field_size]
            if self.wtree.get_widget('fs_'+ss[3]).get_active():
                self.conf.set_new_mode(ss[4])
                self.wtree.get_widget('preferencesdialog1').hide()
                return True
        # it was Custom
        w = self.wtree.get_widget('fs_h').get_value()
        h = self.wtree.get_widget('fs_v').get_value()
        mines = self.wtree.get_widget('fs_mines').get_value()
        if not conf.set_new_mode(GamePrefs.CUSTOM, w, h, mines):
            # TODO: add some message
            gtk.MessageDialog(None, gtk.DIALOG_MODAL,
                              gtk.MESSAGE_ERROR, gtk.BUTTONS_CLOSE,
                              "Setting of custom field size failed, make sure " +
                              "that amount of mines is smaller than width*height.")
            return False
        self.wtree.get_widget('preferencesdialog1').hide()
        return True

        #self.wtree.get_widget('preferencesdialog1').hide()
        
    def on_about1_activate(self, *args):
        self.wtree.get_widget('aboutdialog1').show()

    def on_rules1_activate(self, *args):
        self.wtree.get_widget('rulesdialog1').show()
        
    def on_rulesclosebutton1_clicked(self, *args):        
        self.wtree.get_widget('rulesdialog1').hide()
        
    def on_esmain_destroy(self, *args):
        gtk.main_quit()
        
    def on_quit1_activate(self, *args):
        gtk.main_quit()
        
em = EsMain();
gtk.main();

print "Done."
